/*
This software is distributed under the Simplified BSD License:

Copyright (c) 2008, Chris Venter <chris.venter@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
    	this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
    	this list of conditions and the following disclaimer in the documentation 
    	and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE.
*/
#ifndef GENERIK_OPTIONS_H
#define GENERIK_OPTIONS_H

/** \file options.h
Commandline arguments processing skeleton class
*/

#include <unistd.h>
#include <map>
#include "macros.h"
#include "tracing.h"

#include <vector>

namespace generik
{
	
	template <typename TYPEOUT, typename TYPEIN>
	TYPEOUT convert(const TYPEIN& value)
	{
		std::stringstream ss;
		ss<<value;
		TYPEOUT result;
		ss>>result;
		return result;
	}
	
	template <>
	std::string convert<std::string, std::string>(const std::string& value)
	{
		return value;
	}
	
	template <>
	std::string convert<std::string>(const bool& value)
	{
		if (value) return "true"; else return "false";
	}
	
	template <>
	bool convert<bool>(const std::string& value)
	{
		if (value.empty()) return false;
		if (value == "false") return false;
		if (value == "0") return false;
		return true;
	}
	
	template <>
	int convert<int>(const std::string& value)
	{
		return atoi(value.c_str());
	}
	
	template <>
	double convert<double>(const std::string& value)
	{
		return atof(value.c_str());
	}
	
	struct arg;
	typedef void(*argfunc)(const char*, void*, arg*);
	
	struct arg
	{
		std::string _opt, _brief, _detail;
		argfunc _func;
		void* _object;
		
		arg(const std::string& opt, const std::string& brief, const std::string& detail, argfunc func, void* object)
		: _opt(opt), _brief(brief), _detail(detail)
		, _func(func), _object(object)
		{}
		
		arg(const arg& a)
		: _opt(a._opt), _brief(a._brief), _detail(a._detail)
		, _func(a._func), _object(a._object)
		{}
		
		char flag()
		{
			return _opt[0];
		}
		
		void set(const char* value)
		{
			(*_func)(value, _object, this);
		}
		
	};
	
	struct arglist
	{
		std::vector<arg> args;
		
		unsigned int find_(char flag, unsigned int offset = 0)
		{
			for (unsigned int i = offset; i < args.size(); ++i)
			{
				char f = args[i].flag();
				if (f == flag)
				{
					return i;
				}
			}
			return -1;
		}
		
		arg* find(char flag, unsigned int& offset)
		{
			unsigned int i = find_(flag, offset);
			if (i != -1)
			{
				offset = i + 1;
				arg* v = &(args[i]);
				return v;
			}
			return 0;
		}
		
		arg* last(char flag)
		{
			for (unsigned int i = args.size()-1; i >= 0; --i)
			{
				char f = args[i].flag();
				if (f == flag)
				{
					arg* v = &(args[i]);
					return v;
				}
			}
			return 0;
		}
		
		bool process(char flag, const char* value)
		{
			unsigned int i = find_(flag);
			if (i != -1)
			{
				args[i].set(value);
				return true;
			}
			return false;
		}
		
		void add(const std::string& opt, const std::string& brief, const std::string& detail, argfunc func, void* object)
		{
			args.push_back(arg(opt, brief, detail, func, object));
		}
		
		std::string getoptstr()
		{
			std::string result;
			GENERIK_FOR_EACH(i, args)
			{
				result += i->_opt;
			}
			return result;
		}
		
		std::string usage(const std::string& progname)
		{
			std::stringstream result;
			result<<"Usage: "<<progname<<" ";
			GENERIK_FOR_EACH(i, args)
			{
				result<<i->_brief<<" ";
			}
			result<<"\nwhere\n";
			GENERIK_FOR_EACH(i, args)
			{
				result<<"\t"<<i->_brief<<" "<<i->_detail<<"\n";
			}
			return result.str();
		}
	};
	
#define GENERIK_DEFAULT_USAGE \
	generik::options* self = (generik::options*)object; \
	GENERIK_ERROR(self->usage()); \
	exit(-1);
	
#define GENERIK_UNKNOWN_ARG \
	generik::options* self = (generik::options*)object; \
	GENERIK_ERROR("unknown argument ["<<value<<"] used"); \
	GENERIK_ERROR(self->usage()); \
	exit(-1); \
	
#define GENERIK_SET_REQUIRED_ARG(type, x) \
	type* self = (type*)object; \
	if (!value) \
	{ \
		GENERIK_ERROR(argin->_brief<<": missing required argument"); \
		GENERIK_ERROR(self->usage()); \
		exit(-1); \
	} \
	self->x = generik::convert<typeof(self->x)>(value); \
	GENERIK_TRACE_VALUE(self->x);
	
#define GENERIK_SET_OPTIONAL_ARG(type, x) \
	type* self = (type*)object; \
	if (value) \
	{ \
		self->x = generik::convert<typeof(self->x)>(value); \
		GENERIK_TRACE_VALUE(self->x); \
	}
	
#define GENERIK_OVERFLOW_ARG \
	generik::options* self = (generik::options*)object; \
	if (value) \
	{ \
		GENERIK_ERROR("too many arguments"); \
		GENERIK_ERROR(self->usage()); \
		exit(-1); \
	}
	
#define GENERIK_REQUIRED_ARG_FUNC(type, x) \
	static void set##x(const char* value, void* object, generik::arg* argin) \
	{ \
		GENERIK_TRACER; \
		GENERIK_SET_REQUIRED_ARG(type, x); \
	}
	
#define GENERIK_OPTIONAL_ARG_FUNC(type, x) \
	static void set##x(const char* value, void* object, generik::arg* argin) \
	{ \
		GENERIK_TRACER; \
		GENERIK_SET_OPTIONAL_ARG(type, x); \
	}
	
#define GENERIK_ADD_DEFAULT_USAGE \
	add("h",  "[-h]", "shows this help information", sethelp);
	
#define GENERIK_ADD_DEFAULT_UNKNOWN \
	add("?",  "", "", setunknown);
	
#define GENERIK_ADD_CATCH_OVERFLOW \
	add("",   "", "", catchoverflow);
	
	struct options
	{
		arglist _list;
		int _argc;
		char** _argv;
		
		options()
		: _argc(-1)
		, _argv(0)
		{
			GENERIK_TRACER;
		}

		/** Shows usage information.
		 This method shows usage information on the commandline, and takes
		 an optional quit parameter (true, by default).
		 It has to be implemented by derived classes, and can make use of the provided usage_() method.
		*/
		std::string usage()
		{
			return _list.usage(_argv[0]);
		}
		
		void add(const std::string& opt, const std::string& brief, const std::string& detail, argfunc func)
		{
			_list.add(opt, brief, detail, func, this);
		}
		
		static void sethelp(const char* value, void* object, arg* argin)
		{
			GENERIK_TRACER;
			GENERIK_DEFAULT_USAGE;
		}
		
		static void setunknown(const char* value, void* object, arg* argin)
		{
			GENERIK_TRACER;
			GENERIK_UNKNOWN_ARG;
		}
		
		static void catchoverflow(const char* value, void* object, arg* argin)
		{
			GENERIK_TRACER;
			GENERIK_OVERFLOW_ARG;
		}
		
		/** Parses a flag.
		 This method has to interpret the flag as passed to it by the run() method.
		 It should be checked for validity (ie the default return should be false).
		 If the flag takes additional data, this can be accessed using the optarg
		 variable, as provided by the getopt package.
		 It has to be implemented by derived classes.
		 */
		//virtual bool parse(int opt) = 0;
		virtual void run(int& argc, char**& argv)
		{
			GENERIK_TRACER;
			_argc = argc;
			_argv = argv;
			GENERIK_TRACE_VALUE(_argc);
			GENERIK_TRACE_VALUE(_argv[0]);
			
			std::string optstr = _list.getoptstr();
			GENERIK_TRACE_VALUE(optstr);
			int opt = -1;
			const char* os = optstr.c_str();
			while ( (opt = getopt(_argc, _argv, os)) != -1 )
			{
				/*
				pass the responsibility on to the argfunc.
				It knows best what to do with any parameters it receives.
				*/
				bool found = false;
				if (opt == '?')
				{
					char v[2];
					v[0] = optopt; v[1] = 0;
					found = _list.process('?', v); // ???
				}
				else
				{
					found = _list.process(opt, optarg);
				}
				if (!found)
				{
					GENERIK_ERROR("unknown argument ["<<(char)opt<<"]");
					GENERIK_ERROR(usage());
					exit(-1);
				}
			}
			
			/*
			match up nextarg with 0 parameters, until either runs out.
			if there are more nextargs left, pass them to the last 0 parameter.
			if there are more 0 parameters left, pass 0 to them.
			
			This way, each parameter can deal with arguments and the consequences
			itself.
			If a parameter receives a 0 as arg, it knows there is no input.
			If the last parameter receives more than one arg
			(or if the designer sets up a last catch-overflow 0 parameter), 
			it knows there are excess args.
			*/
			
			unsigned int offset = 0;
			const char* narg = nextarg();
			arg* found = _list.find(0, offset);
			GENERIK_TRACE_VALUE(narg);
			GENERIK_TRACE_VALUE(found);
			while (found)
			{
				GENERIK_TRACE("parsing extra parameter");
				found->set(narg);
				//
				narg = nextarg();
				found = _list.find(0, offset);
			}
			
			found = _list.last(0);
			if (narg && found)
			{
				GENERIK_TRACE("more extra arguments");
				while (narg)
				{
					found->set(narg);
					narg = nextarg();
				}
			}
			
			if (!post())
			{
				GENERIK_ERROR("could not post process the arguments");
				GENERIK_ERROR(usage());
				exit(-1);
			}
		}

		/** Post process method.
		 This method can be implemented to do a post processing of any remainder
		 data, left over after the option parsing is done.
		 It can use remainder() to determine how many strings are left over in argv,
		 and the nextarg() method to automatically extract each remaining argument.
		 */
		virtual bool post()
		{
			return true;
		}
		
		/** Extracts remaining strings.
		 This method returns remaining strings after argument parsing has finished.
		 It returns 0 when no more arguments are left.
		 */
		const char* nextarg()
		{
			const char* val = _argv[optind];
			if (optind++ < _argc) return val;
			return 0;
		}
		/** Returns the number of remaining args.
		 This method returns the number of remaining arguments.
		 */
		unsigned int remainder()
		{
			return _argc - optind;
		}

		/** Dump the extracted data.
		 This method can be implemented to do a default dump of the data extracted
		 by the option parsing.
		 */
		//virtual void dump() {}
	};

}

#endif



